/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Basic Compute Shader Tests.
 *//*--------------------------------------------------------------------*/

#include "es31fBasicComputeShaderTests.hpp"
#include "gluShaderProgram.hpp"
#include "gluObjectWrapper.hpp"
#include "gluRenderContext.hpp"
#include "gluProgramInterfaceQuery.hpp"
#include "gluContextInfo.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "tcuTestLog.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deMemory.h"

namespace deqp
{
namespace gles31
{
namespace Functional
{

using std::string;
using std::vector;
using tcu::TestLog;
using namespace glu;

//! Utility for mapping buffers.
class BufferMemMap
{
public:
	BufferMemMap (const glw::Functions& gl, deUint32 target, int offset, int size, deUint32 access)
		: m_gl		(gl)
		, m_target	(target)
		, m_ptr		(DE_NULL)
	{
		m_ptr = gl.mapBufferRange(target, offset, size, access);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
		TCU_CHECK(m_ptr);
	}

	~BufferMemMap (void)
	{
		m_gl.unmapBuffer(m_target);
	}

	void*	getPtr		(void) const { return m_ptr; }
	void*	operator*	(void) const { return m_ptr; }

private:
							BufferMemMap			(const BufferMemMap& other);
	BufferMemMap&			operator=				(const BufferMemMap& other);

	const glw::Functions&	m_gl;
	const deUint32			m_target;
	void*					m_ptr;
};

namespace
{

class EmptyComputeShaderCase : public TestCase
{
public:
	EmptyComputeShaderCase (Context& context)
		: TestCase(context, "empty", "Empty shader")
	{
	}

	IterateResult iterate (void)
	{
		const GLSLVersion	glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		std::ostringstream	src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< "layout (local_size_x = 1) in;\n"
			   "void main (void) {}\n";

		const ShaderProgram program(m_context.getRenderContext(),
			ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));

		const glw::Functions& gl = m_context.getRenderContext().getFunctions();

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		gl.useProgram(program.getProgram());
		gl.dispatchCompute(1, 1, 1);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}
};

class UBOToSSBOInvertCase : public TestCase
{
public:
	UBOToSSBOInvertCase (Context& context, const char* name, const char* description, int numValues, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
		: TestCase		(context, name, description)
		, m_numValues	(numValues)
		, m_localSize	(localSize)
		, m_workSize	(workSize)
	{
		DE_ASSERT(m_numValues % (m_workSize[0]*m_workSize[1]*m_workSize[2]*m_localSize[0]*m_localSize[1]*m_localSize[2]) == 0);
	}

	IterateResult iterate (void)
	{
		const GLSLVersion	glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		std::ostringstream	src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
			<< "uniform Input {\n"
			<< "    uint values[" << m_numValues << "];\n"
			<< "} ub_in;\n"
			<< "layout(binding = 1) buffer Output {\n"
			<< "    uint values[" << m_numValues << "];\n"
			<< "} sb_out;\n"
			<< "void main (void) {\n"
			<< "    uvec3 size           = gl_NumWorkGroups * gl_WorkGroupSize;\n"
			<< "    uint numValuesPerInv = uint(ub_in.values.length()) / (size.x*size.y*size.z);\n"
			<< "    uint groupNdx        = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
			<< "    uint offset          = numValuesPerInv*groupNdx;\n"
			<< "\n"
			<< "    for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
			<< "        sb_out.values[offset + ndx] = ~ub_in.values[offset + ndx];\n"
			<< "}\n";

		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
		const Buffer				inputBuffer		(m_context.getRenderContext());
		const Buffer				outputBuffer	(m_context.getRenderContext());
		std::vector<deUint32>		inputValues		(m_numValues);

		// Compute input values.
		{
			de::Random rnd(0x111223f);
			for (int ndx = 0; ndx < (int)inputValues.size(); ndx++)
				inputValues[ndx] = rnd.getUint32();
		}

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;

		gl.useProgram(program.getProgram());

		// Input buffer setup
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM_BLOCK, "Input");
			const InterfaceBlockInfo	blockInfo	= getProgramInterfaceBlockInfo(gl, program.getProgram(), GL_UNIFORM_BLOCK, blockIndex);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM, "Input.values");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_UNIFORM, valueIndex);

			gl.bindBuffer(GL_UNIFORM_BUFFER, *inputBuffer);
			gl.bufferData(GL_UNIFORM_BUFFER, (glw::GLsizeiptr)blockInfo.dataSize, DE_NULL, GL_STATIC_DRAW);

			{
				const BufferMemMap bufMap(gl, GL_UNIFORM_BUFFER, 0, (int)blockInfo.dataSize, GL_MAP_WRITE_BIT);

				for (deUint32 ndx = 0; ndx < de::min(valueInfo.arraySize, (deUint32)inputValues.size()); ndx++)
					*(deUint32*)((deUint8*)bufMap.getPtr() + valueInfo.offset + ndx*valueInfo.arrayStride) = inputValues[ndx];
			}

			gl.uniformBlockBinding(program.getProgram(), blockIndex, 0);
			gl.bindBufferBase(GL_UNIFORM_BUFFER, 0, *inputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Input buffer setup failed");
		}

		// Output buffer setup
		{
			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, *outputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
		}

		// Dispatch compute workload
		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		// Read back and compare
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);

			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());
			for (deUint32 ndx = 0; ndx < valueInfo.arraySize; ndx++)
			{
				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*ndx));
				const deUint32	ref		= ~inputValues[ndx];

				if (res != ref)
					throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(ndx) + "]");
			}
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const int			m_numValues;
	const tcu::IVec3	m_localSize;
	const tcu::IVec3	m_workSize;
};

class CopyInvertSSBOCase : public TestCase
{
public:
	CopyInvertSSBOCase (Context& context, const char* name, const char* description, int numValues, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
		: TestCase		(context, name, description)
		, m_numValues	(numValues)
		, m_localSize	(localSize)
		, m_workSize	(workSize)
	{
		DE_ASSERT(m_numValues % (m_workSize[0]*m_workSize[1]*m_workSize[2]*m_localSize[0]*m_localSize[1]*m_localSize[2]) == 0);
	}

	IterateResult iterate (void)
	{
		const GLSLVersion	glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		std::ostringstream	src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
			<< "layout(binding = 0) buffer Input {\n"
			<< "    uint values[" << m_numValues << "];\n"
			<< "} sb_in;\n"
			<< "layout (binding = 1) buffer Output {\n"
			<< "    uint values[" << m_numValues << "];\n"
			<< "} sb_out;\n"
			<< "void main (void) {\n"
			<< "    uvec3 size           = gl_NumWorkGroups * gl_WorkGroupSize;\n"
			<< "    uint numValuesPerInv = uint(sb_in.values.length()) / (size.x*size.y*size.z);\n"
			<< "    uint groupNdx        = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
			<< "    uint offset          = numValuesPerInv*groupNdx;\n"
			<< "\n"
			<< "    for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
			<< "        sb_out.values[offset + ndx] = ~sb_in.values[offset + ndx];\n"
			<< "}\n";

		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
		const Buffer				inputBuffer		(m_context.getRenderContext());
		const Buffer				outputBuffer	(m_context.getRenderContext());
		std::vector<deUint32>		inputValues		(m_numValues);

		// Compute input values.
		{
			de::Random rnd(0x124fef);
			for (int ndx = 0; ndx < (int)inputValues.size(); ndx++)
				inputValues[ndx] = rnd.getUint32();
		}

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;

		gl.useProgram(program.getProgram());

		// Input buffer setup
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Input");
			const InterfaceBlockInfo	blockInfo	= getProgramInterfaceBlockInfo(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Input.values");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *inputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, (glw::GLsizeiptr)blockInfo.dataSize, DE_NULL, GL_STATIC_DRAW);

			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());

			{
				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, (int)blockInfo.dataSize, GL_MAP_WRITE_BIT);

				for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
					*(deUint32*)((deUint8*)bufMap.getPtr() + valueInfo.offset + ndx*valueInfo.arrayStride) = inputValues[ndx];
			}

			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, blockInfo.bufferBinding, *inputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Input buffer setup failed");
		}

		// Output buffer setup
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const InterfaceBlockInfo	blockInfo	= getProgramInterfaceBlockInfo(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockInfo.dataSize, DE_NULL, GL_STREAM_READ);
			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, blockInfo.bufferBinding, *outputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
		}

		// Dispatch compute workload
		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		// Read back and compare
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);

			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());
			for (deUint32 ndx = 0; ndx < valueInfo.arraySize; ndx++)
			{
				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*ndx));
				const deUint32	ref		= ~inputValues[ndx];

				if (res != ref)
					throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(ndx) + "]");
			}
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const int			m_numValues;
	const tcu::IVec3	m_localSize;
	const tcu::IVec3	m_workSize;
};

class InvertSSBOInPlaceCase : public TestCase
{
public:
	InvertSSBOInPlaceCase (Context& context, const char* name, const char* description, int numValues, bool isSized, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
		: TestCase		(context, name, description)
		, m_numValues	(numValues)
		, m_isSized		(isSized)
		, m_localSize	(localSize)
		, m_workSize	(workSize)
	{
		DE_ASSERT(m_numValues % (m_workSize[0]*m_workSize[1]*m_workSize[2]*m_localSize[0]*m_localSize[1]*m_localSize[2]) == 0);
	}

	IterateResult iterate (void)
	{
		const GLSLVersion	glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		std::ostringstream	src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
			<< "layout(binding = 0) buffer InOut {\n"
			<< "    uint values[" << (m_isSized ? de::toString(m_numValues) : string("")) << "];\n"
			<< "} sb_inout;\n"
			<< "void main (void) {\n"
			<< "    uvec3 size           = gl_NumWorkGroups * gl_WorkGroupSize;\n"
			<< "    uint numValuesPerInv = uint(sb_inout.values.length()) / (size.x*size.y*size.z);\n"
			<< "    uint groupNdx        = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
			<< "    uint offset          = numValuesPerInv*groupNdx;\n"
			<< "\n"
			<< "    for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
			<< "        sb_inout.values[offset + ndx] = ~sb_inout.values[offset + ndx];\n"
			<< "}\n";

		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		const Buffer				outputBuffer	(m_context.getRenderContext());
		const deUint32				valueIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "InOut.values");
		const InterfaceVariableInfo	valueInfo		= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
		const deUint32				blockSize		= valueInfo.arrayStride*(deUint32)m_numValues;
		std::vector<deUint32>		inputValues		(m_numValues);

		// Compute input values.
		{
			de::Random rnd(0x82ce7f);
			for (int ndx = 0; ndx < (int)inputValues.size(); ndx++)
				inputValues[ndx] = rnd.getUint32();
		}

		TCU_CHECK(valueInfo.arraySize == (deUint32)(m_isSized ? m_numValues : 0));

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;

		gl.useProgram(program.getProgram());

		// Output buffer setup
		{
			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_DRAW);

			{
				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, (int)blockSize, GL_MAP_WRITE_BIT);

				for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
					*(deUint32*)((deUint8*)bufMap.getPtr() + valueInfo.offset + ndx*valueInfo.arrayStride) = inputValues[ndx];
			}

			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer setup failed");
		}

		// Dispatch compute workload
		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		// Read back and compare
		{
			const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);

			for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
			{
				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*ndx));
				const deUint32	ref		= ~inputValues[ndx];

				if (res != ref)
					throw tcu::TestError(string("Comparison failed for InOut.values[") + de::toString(ndx) + "]");
			}
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const int			m_numValues;
	const bool			m_isSized;
	const tcu::IVec3	m_localSize;
	const tcu::IVec3	m_workSize;
};

class WriteToMultipleSSBOCase : public TestCase
{
public:
	WriteToMultipleSSBOCase (Context& context, const char* name, const char* description, int numValues, bool isSized, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
		: TestCase		(context, name, description)
		, m_numValues	(numValues)
		, m_isSized		(isSized)
		, m_localSize	(localSize)
		, m_workSize	(workSize)
	{
		DE_ASSERT(m_numValues % (m_workSize[0]*m_workSize[1]*m_workSize[2]*m_localSize[0]*m_localSize[1]*m_localSize[2]) == 0);
	}

	IterateResult iterate (void)
	{
		const GLSLVersion	glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		std::ostringstream	src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
			<< "layout(binding = 0) buffer Out0 {\n"
			<< "    uint values[" << (m_isSized ? de::toString(m_numValues) : string("")) << "];\n"
			<< "} sb_out0;\n"
			<< "layout(binding = 1) buffer Out1 {\n"
			<< "    uint values[" << (m_isSized ? de::toString(m_numValues) : string("")) << "];\n"
			<< "} sb_out1;\n"
			<< "void main (void) {\n"
			<< "    uvec3 size      = gl_NumWorkGroups * gl_WorkGroupSize;\n"
			<< "    uint groupNdx   = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
			<< "\n"
			<< "    {\n"
			<< "        uint numValuesPerInv = uint(sb_out0.values.length()) / (size.x*size.y*size.z);\n"
			<< "        uint offset          = numValuesPerInv*groupNdx;\n"
			<< "\n"
			<< "        for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
			<< "            sb_out0.values[offset + ndx] = offset + ndx;\n"
			<< "    }\n"
			<< "    {\n"
			<< "        uint numValuesPerInv = uint(sb_out1.values.length()) / (size.x*size.y*size.z);\n"
			<< "        uint offset          = numValuesPerInv*groupNdx;\n"
			<< "\n"
			<< "        for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
			<< "            sb_out1.values[offset + ndx] = uint(sb_out1.values.length()) - offset - ndx;\n"
			<< "    }\n"
			<< "}\n";

		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		const Buffer				outputBuffer0	(m_context.getRenderContext());
		const deUint32				value0Index		= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Out0.values");
		const InterfaceVariableInfo	value0Info		= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, value0Index);
		const deUint32				block0Size		= value0Info.arrayStride*(deUint32)m_numValues;

		const Buffer				outputBuffer1	(m_context.getRenderContext());
		const deUint32				value1Index		= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Out1.values");
		const InterfaceVariableInfo	value1Info		= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, value1Index);
		const deUint32				block1Size		= value1Info.arrayStride*(deUint32)m_numValues;

		TCU_CHECK(value0Info.arraySize == (deUint32)(m_isSized ? m_numValues : 0));
		TCU_CHECK(value1Info.arraySize == (deUint32)(m_isSized ? m_numValues : 0));

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;

		gl.useProgram(program.getProgram());

		// Output buffer setup
		{
			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer0);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, block0Size, DE_NULL, GL_STREAM_DRAW);

			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer0);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer setup failed");
		}
		{
			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer1);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, block1Size, DE_NULL, GL_STREAM_DRAW);

			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, *outputBuffer1);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Buffer setup failed");
		}

		// Dispatch compute workload
		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		// Read back and compare
		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer0);
		{
			const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, block0Size, GL_MAP_READ_BIT);

			for (deUint32 ndx = 0; ndx < (deUint32)m_numValues; ndx++)
			{
				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + value0Info.offset + value0Info.arrayStride*ndx));
				const deUint32	ref		= ndx;

				if (res != ref)
					throw tcu::TestError(string("Comparison failed for Out0.values[") + de::toString(ndx) + "] res=" + de::toString(res) + " ref=" + de::toString(ref));
			}
		}
		gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer1);
		{
			const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, block1Size, GL_MAP_READ_BIT);

			for (deUint32 ndx = 0; ndx < (deUint32)m_numValues; ndx++)
			{
				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + value1Info.offset + value1Info.arrayStride*ndx));
				const deUint32	ref		= m_numValues - ndx;

				if (res != ref)
					throw tcu::TestError(string("Comparison failed for Out1.values[") + de::toString(ndx) + "] res=" + de::toString(res) + " ref=" + de::toString(ref));
			}
		}
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const int			m_numValues;
	const bool			m_isSized;
	const tcu::IVec3	m_localSize;
	const tcu::IVec3	m_workSize;
};

class SSBOLocalBarrierCase : public TestCase
{
public:
	SSBOLocalBarrierCase (Context& context, const char* name, const char* description, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
		: TestCase		(context, name, description)
		, m_localSize	(localSize)
		, m_workSize	(workSize)
	{
	}

	IterateResult iterate (void)
	{
		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const Buffer				outputBuffer	(m_context.getRenderContext());
		const int					workGroupSize	= m_localSize[0]*m_localSize[1]*m_localSize[2];
		const int					workGroupCount	= m_workSize[0]*m_workSize[1]*m_workSize[2];
		const int					numValues		= workGroupSize*workGroupCount;

		const GLSLVersion			glslVersion		= glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		std::ostringstream			src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
			<< "layout(binding = 0) buffer Output {\n"
			<< "    coherent uint values[" << numValues << "];\n"
			<< "} sb_out;\n\n"
			<< "shared uint offsets[" << workGroupSize << "];\n\n"
			<< "void main (void) {\n"
			<< "    uint localSize  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
			<< "    uint globalNdx  = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
			<< "    uint globalOffs = localSize*globalNdx;\n"
			<< "    uint localOffs  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_LocalInvocationID.z + gl_WorkGroupSize.x*gl_LocalInvocationID.y + gl_LocalInvocationID.x;\n"
			<< "\n"
			<< "    sb_out.values[globalOffs + localOffs] = globalOffs;\n"
			<< "    memoryBarrierBuffer();\n"
			<< "    barrier();\n"
			<< "    sb_out.values[globalOffs + ((localOffs+1u)%localSize)] += localOffs;\n"
			<< "    memoryBarrierBuffer();\n"
			<< "    barrier();\n"
			<< "    sb_out.values[globalOffs + ((localOffs+2u)%localSize)] += localOffs;\n"
			<< "}\n";

		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ComputeSource(src.str()));

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;

		gl.useProgram(program.getProgram());

		// Output buffer setup
		{
			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
		}

		// Dispatch compute workload
		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		// Read back and compare
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);

			for (int groupNdx = 0; groupNdx < workGroupCount; groupNdx++)
			{
				for (int localOffs = 0; localOffs < workGroupSize; localOffs++)
				{
					const int		globalOffs	= groupNdx*workGroupSize;
					const deUint32	res			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*(globalOffs + localOffs)));
					const int		offs0		= localOffs-1 < 0 ? ((localOffs+workGroupSize-1)%workGroupSize) : ((localOffs-1)%workGroupSize);
					const int		offs1		= localOffs-2 < 0 ? ((localOffs+workGroupSize-2)%workGroupSize) : ((localOffs-2)%workGroupSize);
					const deUint32	ref			= (deUint32)(globalOffs + offs0 + offs1);

					if (res != ref)
						throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(globalOffs + localOffs) + "]");
				}
			}
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const tcu::IVec3	m_localSize;
	const tcu::IVec3	m_workSize;
};

class SSBOBarrierCase : public TestCase
{
public:
	SSBOBarrierCase (Context& context, const char* name, const char* description, const tcu::IVec3& workSize)
		: TestCase		(context, name, description)
		, m_workSize	(workSize)
	{
	}

	IterateResult iterate (void)
	{
		const GLSLVersion	glslVersion				= glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		const char* const	glslVersionDeclaration	= getGLSLVersionDeclaration(glslVersion);

		std::ostringstream src0;
		src0 << glslVersionDeclaration << "\n"
			 << "layout (local_size_x = 1) in;\n"
						  "uniform uint u_baseVal;\n"
						  "layout(binding = 1) buffer Output {\n"
						  "    uint values[];\n"
						  "};\n"
						  "void main (void) {\n"
						  "    uint offset = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
						  "    values[offset] = u_baseVal+offset;\n"
				"}\n";

		std::ostringstream src1;
		src1 << glslVersionDeclaration << "\n"
			 << "layout (local_size_x = 1) in;\n"
						  "uniform uint u_baseVal;\n"
						  "layout(binding = 1) buffer Input {\n"
						  "    uint values[];\n"
						  "};\n"
						  "layout(binding = 0) buffer Output {\n"
						  "    coherent uint sum;\n"
						  "};\n"
						  "void main (void) {\n"
						  "    uint offset = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
						  "    uint value  = values[offset];\n"
						  "    atomicAdd(sum, value);\n"
				"}\n";

		const ShaderProgram			program0		(m_context.getRenderContext(), ProgramSources() << ComputeSource(src0.str()));
		const ShaderProgram			program1		(m_context.getRenderContext(), ProgramSources() << ComputeSource(src1.str()));

		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const Buffer				tempBuffer		(m_context.getRenderContext());
		const Buffer				outputBuffer	(m_context.getRenderContext());
		const deUint32				baseValue		= 127;

		m_testCtx.getLog() << program0 << program1;
		if (!program0.isOk() || !program1.isOk())
			TCU_FAIL("Compile failed");

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;

		// Temp buffer setup
		{
			const deUint32				valueIndex		= gl.getProgramResourceIndex(program0.getProgram(), GL_BUFFER_VARIABLE, "values[0]");
			const InterfaceVariableInfo	valueInfo		= getProgramInterfaceVariableInfo(gl, program0.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
			const deUint32				bufferSize		= valueInfo.arrayStride*m_workSize[0]*m_workSize[1]*m_workSize[2];

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *tempBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, (glw::GLsizeiptr)bufferSize, DE_NULL, GL_STATIC_DRAW);
			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, *tempBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Temp buffer setup failed");
		}

		// Output buffer setup
		{
			const deUint32		blockIndex		= gl.getProgramResourceIndex(program1.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int			blockSize		= getProgramResourceInt(gl, program1.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);

			{
				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_WRITE_BIT);
				deMemset(bufMap.getPtr(), 0, blockSize);
			}

			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
		}

		// Dispatch compute workload
		gl.useProgram(program0.getProgram());
		gl.uniform1ui(gl.getUniformLocation(program0.getProgram(), "u_baseVal"), baseValue);
		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
		gl.memoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
		gl.useProgram(program1.getProgram());
		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to dispatch commands");

		// Read back and compare
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program1.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int					blockSize	= getProgramResourceInt(gl, program1.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program1.getProgram(), GL_BUFFER_VARIABLE, "sum");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program1.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);

			const deUint32				res			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset));
			deUint32					ref			= 0;

			for (int ndx = 0; ndx < m_workSize[0]*m_workSize[1]*m_workSize[2]; ndx++)
				ref += baseValue + (deUint32)ndx;

			if (res != ref)
			{
				m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed, expected " << ref << ", got " << res << TestLog::EndMessage;
				throw tcu::TestError("Comparison failed");
			}
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const tcu::IVec3	m_workSize;
};

class BasicSharedVarCase : public TestCase
{
public:
	BasicSharedVarCase (Context& context, const char* name, const char* description, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
		: TestCase		(context, name, description)
		, m_localSize	(localSize)
		, m_workSize	(workSize)
	{
	}

	IterateResult iterate (void)
	{
		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const Buffer				outputBuffer	(m_context.getRenderContext());
		const int					workGroupSize	= m_localSize[0]*m_localSize[1]*m_localSize[2];
		const int					workGroupCount	= m_workSize[0]*m_workSize[1]*m_workSize[2];
		const int					numValues		= workGroupSize*workGroupCount;

		const GLSLVersion			glslVersion		= glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		std::ostringstream			src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
			<< "layout(binding = 0) buffer Output {\n"
			<< "    uint values[" << numValues << "];\n"
			<< "} sb_out;\n\n"
			<< "shared uint offsets[" << workGroupSize << "];\n\n"
			<< "void main (void) {\n"
			<< "    uint localSize  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
			<< "    uint globalNdx  = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
			<< "    uint globalOffs = localSize*globalNdx;\n"
			<< "    uint localOffs  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_LocalInvocationID.z + gl_WorkGroupSize.x*gl_LocalInvocationID.y + gl_LocalInvocationID.x;\n"
			<< "\n"
			<< "    offsets[localSize-localOffs-1u] = globalOffs + localOffs*localOffs;\n"
			<< "    barrier();\n"
			<< "    sb_out.values[globalOffs + localOffs] = offsets[localOffs];\n"
			<< "}\n";

		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;

		gl.useProgram(program.getProgram());

		// Output buffer setup
		{
			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
		}

		// Dispatch compute workload
		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		// Read back and compare
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);

			for (int groupNdx = 0; groupNdx < workGroupCount; groupNdx++)
			{
				for (int localOffs = 0; localOffs < workGroupSize; localOffs++)
				{
					const int		globalOffs	= groupNdx*workGroupSize;
					const deUint32	res			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*(globalOffs + localOffs)));
					const deUint32	ref			= (deUint32)(globalOffs + (workGroupSize-localOffs-1)*(workGroupSize-localOffs-1));

					if (res != ref)
						throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(globalOffs + localOffs) + "]");
				}
			}
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const tcu::IVec3	m_localSize;
	const tcu::IVec3	m_workSize;
};

class SharedVarAtomicOpCase : public TestCase
{
public:
	SharedVarAtomicOpCase (Context& context, const char* name, const char* description, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
		: TestCase		(context, name, description)
		, m_localSize	(localSize)
		, m_workSize	(workSize)
	{
	}

	IterateResult iterate (void)
	{
		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const Buffer				outputBuffer	(m_context.getRenderContext());
		const int					workGroupSize	= m_localSize[0]*m_localSize[1]*m_localSize[2];
		const int					workGroupCount	= m_workSize[0]*m_workSize[1]*m_workSize[2];
		const int					numValues		= workGroupSize*workGroupCount;

		const GLSLVersion			glslVersion		= glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		std::ostringstream			src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
			<< "layout(binding = 0) buffer Output {\n"
			<< "    uint values[" << numValues << "];\n"
			<< "} sb_out;\n\n"
			<< "shared uint count;\n\n"
			<< "void main (void) {\n"
			<< "    uint localSize  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
			<< "    uint globalNdx  = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
			<< "    uint globalOffs = localSize*globalNdx;\n"
			<< "\n"
			<< "    count = 0u;\n"
			<< "    barrier();\n"
			<< "    uint oldVal = atomicAdd(count, 1u);\n"
			<< "    sb_out.values[globalOffs+oldVal] = oldVal+1u;\n"
			<< "}\n";

		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;

		gl.useProgram(program.getProgram());

		// Output buffer setup
		{
			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
		}

		// Dispatch compute workload
		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		// Read back and compare
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);

			for (int groupNdx = 0; groupNdx < workGroupCount; groupNdx++)
			{
				for (int localOffs = 0; localOffs < workGroupSize; localOffs++)
				{
					const int		globalOffs	= groupNdx*workGroupSize;
					const deUint32	res			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*(globalOffs + localOffs)));
					const deUint32	ref			= (deUint32)(localOffs+1);

					if (res != ref)
						throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(globalOffs + localOffs) + "]");
				}
			}
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const tcu::IVec3	m_localSize;
	const tcu::IVec3	m_workSize;
};

class CopyImageToSSBOCase : public TestCase
{
public:
	CopyImageToSSBOCase (Context& context, const char* name, const char* description, const tcu::IVec2& localSize, const tcu::IVec2& imageSize)
		: TestCase		(context, name, description)
		, m_localSize	(localSize)
		, m_imageSize	(imageSize)
	{
		DE_ASSERT(m_imageSize[0] % m_localSize[0] == 0);
		DE_ASSERT(m_imageSize[1] % m_localSize[1] == 0);
	}

	IterateResult iterate (void)
	{
		const GLSLVersion			glslVersion		= glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		std::ostringstream			src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ") in;\n"
			<< "layout(r32ui, binding = 1) readonly uniform highp uimage2D u_srcImg;\n"
			<< "layout(binding = 0) buffer Output {\n"
			<< "    uint values[" << (m_imageSize[0]*m_imageSize[1]) << "];\n"
			<< "} sb_out;\n\n"
			<< "void main (void) {\n"
			<< "    uint stride = gl_NumWorkGroups.x*gl_WorkGroupSize.x;\n"
			<< "    uint value  = imageLoad(u_srcImg, ivec2(gl_GlobalInvocationID.xy)).x;\n"
			<< "    sb_out.values[gl_GlobalInvocationID.y*stride + gl_GlobalInvocationID.x] = value;\n"
			<< "}\n";

		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const Buffer				outputBuffer	(m_context.getRenderContext());
		const Texture				inputTexture	(m_context.getRenderContext());
		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
		const tcu::IVec2			workSize		= m_imageSize / m_localSize;
		de::Random					rnd				(0xab2c7);
		vector<deUint32>			inputValues		(m_imageSize[0]*m_imageSize[1]);

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << workSize << TestLog::EndMessage;

		gl.useProgram(program.getProgram());

		// Input values
		for (vector<deUint32>::iterator i = inputValues.begin(); i != inputValues.end(); ++i)
			*i = rnd.getUint32();

		// Input image setup
		gl.bindTexture(GL_TEXTURE_2D, *inputTexture);
		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_imageSize[0], m_imageSize[1]);
		gl.texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_imageSize[0], m_imageSize[1], GL_RED_INTEGER, GL_UNSIGNED_INT, &inputValues[0]);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading image data failed");

		// Bind to unit 1
		gl.bindImageTexture(1, *inputTexture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Image setup failed");

		// Output buffer setup
		{
			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
		}

		// Dispatch compute workload
		gl.dispatchCompute(workSize[0], workSize[1], 1);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		// Read back and compare
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);

			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());

			for (deUint32 ndx = 0; ndx < valueInfo.arraySize; ndx++)
			{
				const deUint32	res		= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*ndx));
				const deUint32	ref		= inputValues[ndx];

				if (res != ref)
					throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(ndx) + "]");
			}
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const tcu::IVec2	m_localSize;
	const tcu::IVec2	m_imageSize;
};

class CopySSBOToImageCase : public TestCase
{
public:
	CopySSBOToImageCase (Context& context, const char* name, const char* description, const tcu::IVec2& localSize, const tcu::IVec2& imageSize)
		: TestCase		(context, name, description)
		, m_localSize	(localSize)
		, m_imageSize	(imageSize)
	{
		DE_ASSERT(m_imageSize[0] % m_localSize[0] == 0);
		DE_ASSERT(m_imageSize[1] % m_localSize[1] == 0);
	}

	IterateResult iterate (void)
	{
		const GLSLVersion			glslVersion		= glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		std::ostringstream			src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ") in;\n"
			<< "layout(r32ui, binding = 1) writeonly uniform highp uimage2D u_dstImg;\n"
			<< "buffer Input {\n"
			<< "    uint values[" << (m_imageSize[0]*m_imageSize[1]) << "];\n"
			<< "} sb_in;\n\n"
			<< "void main (void) {\n"
			<< "    uint stride = gl_NumWorkGroups.x*gl_WorkGroupSize.x;\n"
			<< "    uint value  = sb_in.values[gl_GlobalInvocationID.y*stride + gl_GlobalInvocationID.x];\n"
			<< "    imageStore(u_dstImg, ivec2(gl_GlobalInvocationID.xy), uvec4(value, 0, 0, 0));\n"
			<< "}\n";

		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const Buffer				inputBuffer		(m_context.getRenderContext());
		const Texture				outputTexture	(m_context.getRenderContext());
		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
		const tcu::IVec2			workSize		= m_imageSize / m_localSize;
		de::Random					rnd				(0x77238ac2);
		vector<deUint32>			inputValues		(m_imageSize[0]*m_imageSize[1]);

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << workSize << TestLog::EndMessage;

		gl.useProgram(program.getProgram());

		// Input values
		for (vector<deUint32>::iterator i = inputValues.begin(); i != inputValues.end(); ++i)
			*i = rnd.getUint32();

		// Input buffer setup
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Input");
			const InterfaceBlockInfo	blockInfo	= getProgramInterfaceBlockInfo(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Input.values");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *inputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, (glw::GLsizeiptr)blockInfo.dataSize, DE_NULL, GL_STATIC_DRAW);

			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());

			{
				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, (int)blockInfo.dataSize, GL_MAP_WRITE_BIT);

				for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
					*(deUint32*)((deUint8*)bufMap.getPtr() + valueInfo.offset + ndx*valueInfo.arrayStride) = inputValues[ndx];
			}

			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, blockInfo.bufferBinding, *inputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Input buffer setup failed");
		}

		// Output image setup
		gl.bindTexture(GL_TEXTURE_2D, *outputTexture);
		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_imageSize[0], m_imageSize[1]);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading image data failed");

		// Bind to unit 1
		gl.bindImageTexture(1, *outputTexture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Image setup failed");

		// Dispatch compute workload
		gl.dispatchCompute(workSize[0], workSize[1], 1);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		// Read back and compare
		{
			Framebuffer			fbo			(m_context.getRenderContext());
			vector<deUint32>	pixels		(inputValues.size()*4);

			gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
			gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *outputTexture, 0);
			TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);

			// \note In ES3 we have to use GL_RGBA_INTEGER
			gl.readBuffer(GL_COLOR_ATTACHMENT0);
			gl.readPixels(0, 0, m_imageSize[0], m_imageSize[1], GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels failed");

			for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
			{
				const deUint32	res		= pixels[ndx*4];
				const deUint32	ref		= inputValues[ndx];

				if (res != ref)
					throw tcu::TestError(string("Comparison failed for pixel ") + de::toString(ndx));
			}
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const tcu::IVec2	m_localSize;
	const tcu::IVec2	m_imageSize;
};

class ImageAtomicOpCase : public TestCase
{
public:
	ImageAtomicOpCase (Context& context, const char* name, const char* description, int localSize, const tcu::IVec2& imageSize)
		: TestCase		(context, name, description)
		, m_localSize	(localSize)
		, m_imageSize	(imageSize)
	{
	}

	void init (void)
	{
		auto contextType = m_context.getRenderContext().getType();
		if (!glu::contextSupports(contextType, glu::ApiType::es(3, 2)) &&
			!glu::contextSupports(contextType, glu::ApiType::core(4, 5)) &&
			!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic"))
			TCU_THROW(NotSupportedError, "Test requires OES_shader_image_atomic extension");
	}

	IterateResult iterate (void)
	{
		glu::ContextType			contextType			= m_context.getRenderContext().getType();
		const GLSLVersion			glslVersion			= glu::getContextTypeGLSLVersion(contextType);
		const bool					supportsES32orGL45	= glu::contextSupports(contextType, glu::ApiType::es(3, 2)) ||
														  glu::contextSupports(contextType, glu::ApiType::core(4, 5));
		std::ostringstream			src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< (supportsES32orGL45 ? "\n" : "#extension GL_OES_shader_image_atomic : require\n")
			<< "layout (local_size_x = " << m_localSize << ") in;\n"
			<< "layout(r32ui, binding = 1) uniform highp uimage2D u_dstImg;\n"
			<< "buffer Input {\n"
			<< "    uint values[" << (m_imageSize[0]*m_imageSize[1]*m_localSize) << "];\n"
			<< "} sb_in;\n\n"
			<< "void main (void) {\n"
			<< "    uint stride = gl_NumWorkGroups.x*gl_WorkGroupSize.x;\n"
			<< "    uint value  = sb_in.values[gl_GlobalInvocationID.y*stride + gl_GlobalInvocationID.x];\n"
			<< "\n"
			<< "    if (gl_LocalInvocationIndex == 0u)\n"
			<< "        imageStore(u_dstImg, ivec2(gl_WorkGroupID.xy), uvec4(0));\n"
			<< "    barrier();\n"
			<< "    imageAtomicAdd(u_dstImg, ivec2(gl_WorkGroupID.xy), value);\n"
			<< "}\n";

		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const Buffer				inputBuffer		(m_context.getRenderContext());
		const Texture				outputTexture	(m_context.getRenderContext());
		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ShaderSource(SHADERTYPE_COMPUTE, src.str()));
		de::Random					rnd				(0x77238ac2);
		vector<deUint32>			inputValues		(m_imageSize[0]*m_imageSize[1]*m_localSize);

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_imageSize << TestLog::EndMessage;

		gl.useProgram(program.getProgram());

		// Input values
		for (vector<deUint32>::iterator i = inputValues.begin(); i != inputValues.end(); ++i)
			*i = rnd.getUint32();

		// Input buffer setup
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Input");
			const InterfaceBlockInfo	blockInfo	= getProgramInterfaceBlockInfo(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Input.values");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *inputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, (glw::GLsizeiptr)blockInfo.dataSize, DE_NULL, GL_STATIC_DRAW);

			TCU_CHECK(valueInfo.arraySize == (deUint32)inputValues.size());

			{
				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, (int)blockInfo.dataSize, GL_MAP_WRITE_BIT);

				for (deUint32 ndx = 0; ndx < (deUint32)inputValues.size(); ndx++)
					*(deUint32*)((deUint8*)bufMap.getPtr() + valueInfo.offset + ndx*valueInfo.arrayStride) = inputValues[ndx];
			}

			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, blockInfo.bufferBinding, *inputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Input buffer setup failed");
		}

		// Output image setup
		gl.bindTexture(GL_TEXTURE_2D, *outputTexture);
		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_imageSize[0], m_imageSize[1]);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading image data failed");

		// Bind to unit 1
		gl.bindImageTexture(1, *outputTexture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Image setup failed");

		// Dispatch compute workload
		gl.dispatchCompute(m_imageSize[0], m_imageSize[1], 1);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		// Read back and compare
		{
			Framebuffer			fbo			(m_context.getRenderContext());
			vector<deUint32>	pixels		(m_imageSize[0]*m_imageSize[1]*4);

			gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
			gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *outputTexture, 0);
			TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);

			// \note In ES3 we have to use GL_RGBA_INTEGER
			gl.readBuffer(GL_COLOR_ATTACHMENT0);
			gl.readPixels(0, 0, m_imageSize[0], m_imageSize[1], GL_RGBA_INTEGER, GL_UNSIGNED_INT, &pixels[0]);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels failed");

			for (int pixelNdx = 0; pixelNdx < (int)inputValues.size()/m_localSize; pixelNdx++)
			{
				const deUint32	res		= pixels[pixelNdx*4];
				deUint32		ref		= 0;

				for (int offs = 0; offs < m_localSize; offs++)
					ref += inputValues[pixelNdx*m_localSize + offs];

				if (res != ref)
					throw tcu::TestError(string("Comparison failed for pixel ") + de::toString(pixelNdx));
			}
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const int			m_localSize;
	const tcu::IVec2	m_imageSize;
};

class ImageBarrierCase : public TestCase
{
public:
	ImageBarrierCase (Context& context, const char* name, const char* description, const tcu::IVec2& workSize)
		: TestCase		(context, name, description)
		, m_workSize	(workSize)
	{
	}

	IterateResult iterate (void)
	{
		const GLSLVersion			glslVersion				= glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		const char* const			glslVersionDeclaration	= getGLSLVersionDeclaration(glslVersion);

		std::ostringstream src0;
		src0 << glslVersionDeclaration << "\n"
			 << "layout (local_size_x = 1) in;\n"
						  "uniform uint u_baseVal;\n"
						  "layout(r32ui, binding = 2) writeonly uniform highp uimage2D u_img;\n"
						  "void main (void) {\n"
						  "    uint offset = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
						  "    imageStore(u_img, ivec2(gl_WorkGroupID.xy), uvec4(offset+u_baseVal, 0, 0, 0));\n"
				"}\n";

		std::ostringstream src1;
		src1 << glslVersionDeclaration << "\n"
			 << "layout (local_size_x = 1) in;\n"
						  "layout(r32ui, binding = 2) readonly uniform highp uimage2D u_img;\n"
						  "layout(binding = 0) buffer Output {\n"
						  "    coherent uint sum;\n"
						  "};\n"
						  "void main (void) {\n"
						  "    uint value = imageLoad(u_img, ivec2(gl_WorkGroupID.xy)).x;\n"
						  "    atomicAdd(sum, value);\n"
				"}\n";

		const ShaderProgram			program0		(m_context.getRenderContext(), ProgramSources() << ComputeSource(src0.str()));
		const ShaderProgram			program1		(m_context.getRenderContext(), ProgramSources() << ComputeSource(src1.str()));

		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const Texture				tempTexture		(m_context.getRenderContext());
		const Buffer				outputBuffer	(m_context.getRenderContext());
		const deUint32				baseValue		= 127;

		m_testCtx.getLog() << program0 << program1;
		if (!program0.isOk() || !program1.isOk())
			TCU_FAIL("Compile failed");

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;

		// Temp texture setup
		gl.bindTexture(GL_TEXTURE_2D, *tempTexture);
		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, m_workSize[0], m_workSize[1]);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading image data failed");

		// Bind to unit 2
		gl.bindImageTexture(2, *tempTexture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Image setup failed");

		// Output buffer setup
		{
			const deUint32		blockIndex		= gl.getProgramResourceIndex(program1.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int			blockSize		= getProgramResourceInt(gl, program1.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);

			{
				const BufferMemMap bufMap(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_WRITE_BIT);
				deMemset(bufMap.getPtr(), 0, blockSize);
			}

			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
		}

		// Dispatch compute workload
		gl.useProgram(program0.getProgram());
		gl.uniform1ui(gl.getUniformLocation(program0.getProgram(), "u_baseVal"), baseValue);
		gl.dispatchCompute(m_workSize[0], m_workSize[1], 1);
		gl.memoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
		gl.useProgram(program1.getProgram());
		gl.dispatchCompute(m_workSize[0], m_workSize[1], 1);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to dispatch commands");

		// Read back and compare
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program1.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int					blockSize	= getProgramResourceInt(gl, program1.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program1.getProgram(), GL_BUFFER_VARIABLE, "sum");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program1.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);

			const deUint32				res			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset));
			deUint32					ref			= 0;

			for (int ndx = 0; ndx < m_workSize[0]*m_workSize[1]; ndx++)
				ref += baseValue + (deUint32)ndx;

			if (res != ref)
			{
				m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed, expected " << ref << ", got " << res << TestLog::EndMessage;
				throw tcu::TestError("Comparison failed");
			}
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const tcu::IVec2	m_workSize;
};

class AtomicCounterCase : public TestCase
{
public:
	AtomicCounterCase (Context& context, const char* name, const char* description, const tcu::IVec3& localSize, const tcu::IVec3& workSize)
		: TestCase		(context, name, description)
		, m_localSize	(localSize)
		, m_workSize	(workSize)
	{
	}

	IterateResult iterate (void)
	{
		const glw::Functions&		gl				= m_context.getRenderContext().getFunctions();
		const Buffer				outputBuffer	(m_context.getRenderContext());
		const Buffer				counterBuffer	(m_context.getRenderContext());
		const int					workGroupSize	= m_localSize[0]*m_localSize[1]*m_localSize[2];
		const int					workGroupCount	= m_workSize[0]*m_workSize[1]*m_workSize[2];
		const int					numValues		= workGroupSize*workGroupCount;

		const GLSLVersion			glslVersion		= glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
		std::ostringstream			src;

		src << getGLSLVersionDeclaration(glslVersion) << "\n"
			<< "layout (local_size_x = " << m_localSize[0] << ", local_size_y = " << m_localSize[1] << ", local_size_z = " << m_localSize[2] << ") in;\n"
			<< "layout(binding = 0) buffer Output {\n"
			<< "    uint values[" << numValues << "];\n"
			<< "} sb_out;\n\n"
			<< "layout(binding = 0, offset = 0) uniform atomic_uint u_count;\n\n"
			<< "void main (void) {\n"
			<< "    uint localSize  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
			<< "    uint globalNdx  = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
			<< "    uint globalOffs = localSize*globalNdx;\n"
			<< "    uint localOffs  = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_LocalInvocationID.z + gl_WorkGroupSize.x*gl_LocalInvocationID.y + gl_LocalInvocationID.x;\n"
			<< "\n"
			<< "    uint oldVal = atomicCounterIncrement(u_count);\n"
			<< "    sb_out.values[globalOffs+localOffs] = oldVal;\n"
			<< "}\n";

		const ShaderProgram			program			(m_context.getRenderContext(), ProgramSources() << ComputeSource(src.str()));

		m_testCtx.getLog() << program;
		if (!program.isOk())
			TCU_FAIL("Compile failed");

		m_testCtx.getLog() << TestLog::Message << "Work groups: " << m_workSize << TestLog::EndMessage;

		gl.useProgram(program.getProgram());

		// Atomic counter buffer setup
		{
			const deUint32	uniformIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_count");
			const deUint32	bufferIndex		= getProgramResourceUint(gl, program.getProgram(), GL_UNIFORM, uniformIndex, GL_ATOMIC_COUNTER_BUFFER_INDEX);
			const deUint32	bufferSize		= getProgramResourceUint(gl, program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferIndex, GL_BUFFER_DATA_SIZE);

			gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, *counterBuffer);
			gl.bufferData(GL_ATOMIC_COUNTER_BUFFER, bufferSize, DE_NULL, GL_STREAM_READ);

			{
				const BufferMemMap memMap(gl, GL_ATOMIC_COUNTER_BUFFER, 0, bufferSize, GL_MAP_WRITE_BIT);
				deMemset(memMap.getPtr(), 0, (int)bufferSize);
			}

			gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, *counterBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Atomic counter buffer setup failed");
		}

		// Output buffer setup
		{
			const deUint32		blockIndex		= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int			blockSize		= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);

			gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer);
			gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ);
			gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer);
			GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
		}

		// Dispatch compute workload
		gl.dispatchCompute(m_workSize[0], m_workSize[1], m_workSize[2]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute()");

		// Read back and compare atomic counter
		{
			const deUint32		uniformIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_count");
			const deUint32		uniformOffset	= getProgramResourceUint(gl, program.getProgram(), GL_UNIFORM, uniformIndex, GL_OFFSET);
			const deUint32		bufferIndex		= getProgramResourceUint(gl, program.getProgram(), GL_UNIFORM, uniformIndex, GL_ATOMIC_COUNTER_BUFFER_INDEX);
			const deUint32		bufferSize		= getProgramResourceUint(gl, program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferIndex, GL_BUFFER_DATA_SIZE);
			const BufferMemMap	bufMap			(gl, GL_ATOMIC_COUNTER_BUFFER, 0, bufferSize, GL_MAP_READ_BIT);

			const deUint32		resVal			= *((const deUint32*)((const deUint8*)bufMap.getPtr() + uniformOffset));

			if (resVal != (deUint32)numValues)
				throw tcu::TestError("Invalid atomic counter value");
		}

		// Read back and compare SSBO
		{
			const deUint32				blockIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output");
			const int					blockSize	= getProgramResourceInt(gl, program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE);
			const deUint32				valueIndex	= gl.getProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.values");
			const InterfaceVariableInfo	valueInfo	= getProgramInterfaceVariableInfo(gl, program.getProgram(), GL_BUFFER_VARIABLE, valueIndex);
			const BufferMemMap			bufMap		(gl, GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT);
			deUint32					valSum		= 0;
			deUint32					refSum		= 0;

			for (int valNdx = 0; valNdx < numValues; valNdx++)
			{
				const deUint32 res = *((const deUint32*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*valNdx));

				valSum += res;
				refSum += (deUint32)valNdx;

				if (!de::inBounds<deUint32>(res, 0, (deUint32)numValues))
					throw tcu::TestError(string("Comparison failed for Output.values[") + de::toString(valNdx) + "]");
			}

			if (valSum != refSum)
				throw tcu::TestError("Total sum of values in Output.values doesn't match");
		}

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}

private:
	const tcu::IVec3	m_localSize;
	const tcu::IVec3	m_workSize;
};

} // anonymous

BasicComputeShaderTests::BasicComputeShaderTests (Context& context)
	: TestCaseGroup(context, "basic", "Basic Compute Shader Tests")
{
}

BasicComputeShaderTests::~BasicComputeShaderTests (void)
{
}

void BasicComputeShaderTests::init (void)
{
	addChild(new EmptyComputeShaderCase(m_context));

	addChild(new UBOToSSBOInvertCase	(m_context, "ubo_to_ssbo_single_invocation",			"Copy from UBO to SSBO, inverting bits",	256,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
	addChild(new UBOToSSBOInvertCase	(m_context, "ubo_to_ssbo_single_group",					"Copy from UBO to SSBO, inverting bits",	1024,	tcu::IVec3(2,1,4),	tcu::IVec3(1,1,1)));
	addChild(new UBOToSSBOInvertCase	(m_context, "ubo_to_ssbo_multiple_invocations",			"Copy from UBO to SSBO, inverting bits",	1024,	tcu::IVec3(1,1,1),	tcu::IVec3(2,4,1)));
	addChild(new UBOToSSBOInvertCase	(m_context, "ubo_to_ssbo_multiple_groups",				"Copy from UBO to SSBO, inverting bits",	1024,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));

	addChild(new CopyInvertSSBOCase		(m_context, "copy_ssbo_single_invocation",				"Copy between SSBOs, inverting bits",	256,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
	addChild(new CopyInvertSSBOCase		(m_context, "copy_ssbo_multiple_invocations",			"Copy between SSBOs, inverting bits",	1024,	tcu::IVec3(1,1,1),	tcu::IVec3(2,4,1)));
	addChild(new CopyInvertSSBOCase		(m_context, "copy_ssbo_multiple_groups",				"Copy between SSBOs, inverting bits",	1024,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));

	addChild(new InvertSSBOInPlaceCase	(m_context, "ssbo_rw_single_invocation",				"Read and write same SSBO",				256,	true,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
	addChild(new InvertSSBOInPlaceCase	(m_context, "ssbo_rw_multiple_groups",					"Read and write same SSBO",				1024,	true,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));

	addChild(new InvertSSBOInPlaceCase	(m_context, "ssbo_unsized_arr_single_invocation",		"Read and write same SSBO",				256,	false,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
	addChild(new InvertSSBOInPlaceCase	(m_context, "ssbo_unsized_arr_multiple_groups",			"Read and write same SSBO",				1024,	false,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));

	addChild(new WriteToMultipleSSBOCase(m_context, "write_multiple_arr_single_invocation",		"Write to multiple SSBOs",				256,	true,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
	addChild(new WriteToMultipleSSBOCase(m_context, "write_multiple_arr_multiple_groups",		"Write to multiple SSBOs",				1024,	true,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));

	addChild(new WriteToMultipleSSBOCase(m_context, "write_multiple_unsized_arr_single_invocation",	"Write to multiple SSBOs",			256,	false,	tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
	addChild(new WriteToMultipleSSBOCase(m_context, "write_multiple_unsized_arr_multiple_groups",	"Write to multiple SSBOs",			1024,	false,	tcu::IVec3(1,4,2),	tcu::IVec3(2,2,4)));

	addChild(new SSBOLocalBarrierCase	(m_context, "ssbo_local_barrier_single_invocation",		"SSBO local barrier usage",				tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
	addChild(new SSBOLocalBarrierCase	(m_context, "ssbo_local_barrier_single_group",			"SSBO local barrier usage",				tcu::IVec3(3,2,5),	tcu::IVec3(1,1,1)));
	addChild(new SSBOLocalBarrierCase	(m_context, "ssbo_local_barrier_multiple_groups",		"SSBO local barrier usage",				tcu::IVec3(3,4,1),	tcu::IVec3(2,7,3)));

	addChild(new SSBOBarrierCase		(m_context, "ssbo_cmd_barrier_single",					"SSBO memory barrier usage",			tcu::IVec3(1,1,1)));
	addChild(new SSBOBarrierCase		(m_context, "ssbo_cmd_barrier_multiple",				"SSBO memory barrier usage",			tcu::IVec3(11,5,7)));

	addChild(new BasicSharedVarCase		(m_context, "shared_var_single_invocation",				"Basic shared variable usage",			tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
	addChild(new BasicSharedVarCase		(m_context, "shared_var_single_group",					"Basic shared variable usage",			tcu::IVec3(3,2,5),	tcu::IVec3(1,1,1)));
	addChild(new BasicSharedVarCase		(m_context, "shared_var_multiple_invocations",			"Basic shared variable usage",			tcu::IVec3(1,1,1),	tcu::IVec3(2,5,4)));
	addChild(new BasicSharedVarCase		(m_context, "shared_var_multiple_groups",				"Basic shared variable usage",			tcu::IVec3(3,4,1),	tcu::IVec3(2,7,3)));

	addChild(new SharedVarAtomicOpCase	(m_context, "shared_atomic_op_single_invocation",		"Atomic operation with shared var",		tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
	addChild(new SharedVarAtomicOpCase	(m_context, "shared_atomic_op_single_group",			"Atomic operation with shared var",		tcu::IVec3(3,2,5),	tcu::IVec3(1,1,1)));
	addChild(new SharedVarAtomicOpCase	(m_context, "shared_atomic_op_multiple_invocations",	"Atomic operation with shared var",		tcu::IVec3(1,1,1),	tcu::IVec3(2,5,4)));
	addChild(new SharedVarAtomicOpCase	(m_context, "shared_atomic_op_multiple_groups",			"Atomic operation with shared var",		tcu::IVec3(3,4,1),	tcu::IVec3(2,7,3)));

	addChild(new CopyImageToSSBOCase	(m_context, "copy_image_to_ssbo_small",					"Image to SSBO copy",					tcu::IVec2(1,1),	tcu::IVec2(64,64)));
	addChild(new CopyImageToSSBOCase	(m_context, "copy_image_to_ssbo_large",					"Image to SSBO copy",					tcu::IVec2(2,4),	tcu::IVec2(512,512)));

	addChild(new CopySSBOToImageCase	(m_context, "copy_ssbo_to_image_small",					"SSBO to image copy",					tcu::IVec2(1,1),	tcu::IVec2(64,64)));
	addChild(new CopySSBOToImageCase	(m_context, "copy_ssbo_to_image_large",					"SSBO to image copy",					tcu::IVec2(2,4),	tcu::IVec2(512,512)));

	addChild(new ImageAtomicOpCase		(m_context, "image_atomic_op_local_size_1",				"Atomic operation with image",			1,	tcu::IVec2(64,64)));
	addChild(new ImageAtomicOpCase		(m_context, "image_atomic_op_local_size_8",				"Atomic operation with image",			8,	tcu::IVec2(64,64)));

	addChild(new ImageBarrierCase		(m_context, "image_barrier_single",						"Image barrier",						tcu::IVec2(1,1)));
	addChild(new ImageBarrierCase		(m_context, "image_barrier_multiple",					"Image barrier",						tcu::IVec2(64,64)));

	addChild(new AtomicCounterCase		(m_context, "atomic_counter_single_invocation",			"Basic atomic counter test",			tcu::IVec3(1,1,1),	tcu::IVec3(1,1,1)));
	addChild(new AtomicCounterCase		(m_context, "atomic_counter_single_group",				"Basic atomic counter test",			tcu::IVec3(3,2,5),	tcu::IVec3(1,1,1)));
	addChild(new AtomicCounterCase		(m_context, "atomic_counter_multiple_invocations",		"Basic atomic counter test",			tcu::IVec3(1,1,1),	tcu::IVec3(2,5,4)));
	addChild(new AtomicCounterCase		(m_context, "atomic_counter_multiple_groups",			"Basic atomic counter test",			tcu::IVec3(3,4,1),	tcu::IVec3(2,7,3)));
}

} // Functional
} // gles31
} // deqp
